// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "zencore.h"

#include <zencore/iobuffer.h>
#include <zencore/string.h>

#include <filesystem>
#include <functional>

namespace zen {

class IoBuffer;
class CompositeBuffer;

/** Delete directory (after deleting any contents)
 */
ZENCORE_API bool DeleteDirectories(const std::filesystem::path& dir);

/** Ensure directory exists.

	Will also create any required parent directories
 */
ZENCORE_API bool CreateDirectories(const std::filesystem::path& dir);

/** Ensure directory exists and delete contents (if any) before returning
 */
ZENCORE_API bool CleanDirectory(const std::filesystem::path& dir);

/** Ensure directory exists and delete contents (if any) before returning
 */
ZENCORE_API bool CleanDirectoryExceptDotFiles(const std::filesystem::path& dir);

/** Map native file handle to a path
 */
ZENCORE_API std::filesystem::path PathFromHandle(void* NativeHandle);

/** Map native file handle to a path
 */
ZENCORE_API std::filesystem::path PathFromHandle(void* NativeHandle, std::error_code& Ec);

/** Get canonical path name from a generic path
 */
ZENCORE_API std::filesystem::path CanonicalPath(std::filesystem::path InPath, std::error_code& Ec);

/** Query file size from native file handle
 */
ZENCORE_API uint64_t FileSizeFromHandle(void* NativeHandle);

ZENCORE_API std::filesystem::path GetRunningExecutablePath();

/** Set the max open file handle count to max allowed for the current process on Linux and MacOS
 */
ZENCORE_API void MaximizeOpenFileCount();

struct FileContents
{
	std::vector<IoBuffer> Data;
	std::error_code		  ErrorCode;

	IoBuffer Flatten();
};

ZENCORE_API FileContents ReadStdIn();

/** Prepare file for reading

	Note that this generally does not actually read the file contents. Instead it creates an
	IoBuffer referencing the file contents so that it may be read at a later time. This is
	leveraged to allow sending of data straight from disk cache and other optimizations.
  */
ZENCORE_API FileContents ReadFile(std::filesystem::path Path);

ZENCORE_API bool ScanFile(std::filesystem::path Path, uint64_t ChunkSize, std::function<void(const void* Data, size_t Size)>&& ProcessFunc);
ZENCORE_API void WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t BufferCount);
ZENCORE_API void WriteFile(std::filesystem::path Path, IoBuffer Data);
ZENCORE_API void WriteFile(std::filesystem::path Path, CompositeBuffer Data);
ZENCORE_API bool MoveToFile(std::filesystem::path Path, IoBuffer Data);
ZENCORE_API void ScanFile(void*												   NativeHandle,
						  uint64_t											   Offset,
						  uint64_t											   Size,
						  uint64_t											   ChunkSize,
						  std::function<void(const void* Data, size_t Size)>&& ProcessFunc);

struct CopyFileOptions
{
	bool EnableClone = true;
	bool MustClone	 = false;
};

ZENCORE_API bool CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
ZENCORE_API void CopyFile(std::filesystem::path	 FromPath,
						  std::filesystem::path	 ToPath,
						  const CopyFileOptions& Options,
						  std::error_code&		 OutError);
ZENCORE_API void CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
ZENCORE_API bool SupportsBlockRefCounting(std::filesystem::path Path);

ZENCORE_API void PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out);
ZENCORE_API std::string PathToUtf8(const std::filesystem::path& Path);

extern template class StringBuilderImpl<std::filesystem::path::value_type>;

/**
 * Helper class for building paths. Backed by a string builder.
 *
 */
class PathBuilderBase : public StringBuilderImpl<std::filesystem::path::value_type>
{
private:
	using Super = StringBuilderImpl<std::filesystem::path::value_type>;

protected:
	using CharType = std::filesystem::path::value_type;
	using ViewType = std::basic_string_view<CharType>;

public:
	void Append(const std::filesystem::path& Rhs) { Super::Append(Rhs.c_str()); }
	void operator/=(const std::filesystem::path& Rhs) { this->operator/=(Rhs.c_str()); };
	void operator/=(const CharType* Rhs)
	{
		AppendSeparator();
		Super::Append(Rhs);
	}
									 operator ViewType() const { return ToView(); }
	std::basic_string_view<CharType> ToView() const { return std::basic_string_view<CharType>(Data(), Size()); }
	std::filesystem::path			 ToPath() const { return std::filesystem::path(ToView()); }

	std::string ToUtf8() const
	{
#if ZEN_PLATFORM_WINDOWS
		return WideToUtf8(ToView());
#else
		return std::string(ToView());
#endif
	}

	void AppendSeparator()
	{
		if (ToView().ends_with(std::filesystem::path::preferred_separator)
#if ZEN_PLATFORM_WINDOWS
			|| ToView().ends_with('/')
#endif
		)
			return;

		Super::Append(std::filesystem::path::preferred_separator);
	}
};

template<size_t N>
class PathBuilder : public PathBuilderBase
{
public:
	PathBuilder() { Init(m_Buffer, N); }

private:
	PathBuilderBase::CharType m_Buffer[N];
};

template<size_t N>
class ExtendablePathBuilder : public PathBuilder<N>
{
public:
	ExtendablePathBuilder() { this->m_IsExtendable = true; }
};

struct DiskSpace
{
	uint64_t Free{};
	uint64_t Total{};
};

ZENCORE_API DiskSpace DiskSpaceInfo(std::filesystem::path Directory, std::error_code& Error);

inline bool
DiskSpaceInfo(std::filesystem::path Directory, DiskSpace& Space)
{
	std::error_code Err;
	Space = DiskSpaceInfo(Directory, Err);
	return !Err;
}

/**
 * Efficient file system traversal
 *
 * Uses the best available mechanism for the platform in question and could take
 * advantage of any file system tracking mechanisms in the future
 *
 */
class FileSystemTraversal
{
public:
	struct TreeVisitor
	{
		using path_view	  = std::basic_string_view<std::filesystem::path::value_type>;
		using path_string = std::filesystem::path::string_type;

		virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize) = 0;

		// This should return true if we should recurse into the directory
		virtual bool VisitDirectory(const std::filesystem::path& Parent, const path_view& DirectoryName) = 0;
	};

	void TraverseFileSystem(const std::filesystem::path& RootDir, TreeVisitor& Visitor);
};

struct DirectoryContent
{
	static const uint8_t			   IncludeDirsFlag	= 1u << 0;
	static const uint8_t			   IncludeFilesFlag = 1u << 1;
	static const uint8_t			   RecursiveFlag	= 1u << 2;
	std::vector<std::filesystem::path> Files;
	std::vector<std::filesystem::path> Directories;
};

void GetDirectoryContent(const std::filesystem::path& RootDir, uint8_t Flags, DirectoryContent& OutContent);

std::string GetEnvVariable(std::string_view VariableName);

std::filesystem::path SearchPathForExecutable(std::string_view ExecutableName);

std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles);
std::error_code RotateDirectories(const std::filesystem::path& DirectoryName, std::size_t MaxDirectories);

//////////////////////////////////////////////////////////////////////////

void filesystem_forcelink();  // internal

}  // namespace zen
